﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using Dycox.ComponentModel;
using Dycox.Linq;

namespace Dycox.Workflows
{
    partial class WFProcess  : SupportAppData, IWorkflowComponent, ICloneable
    {
        internal const int MainThreadId = 0;

        internal static WFProcess Create(Workflow workflow)
        {
            WFProcess process = new WFProcess();
            process.ProcessId = Guid.NewGuid();
            process.WorkflowName = workflow.Name;
            process.CreationTime = WorkflowManager.TimeService.Now;
            process.DisplayName = workflow.DisplayName;
            process._properties = new ProcessProperties(workflow.CurrentVersion.ToString());
            process.SetupProperties(true);
            return process;
        }

        internal static WFProcess Create(Workflow workflow, WFProcess parent)
        {
            WFProcess process = Create(workflow);

            process._parentProcessId = parent._ProcessId;

            return process;
        }

        internal WFThread NewThread()
        {
            WFThread thread = new WFThread();
            thread.ThreadId = this.ThreadSeed++;
            thread.CreationTime = WorkflowManager.TimeService.Now;
            thread.Process = this;
            return thread;
        }

        internal WFFrame NewFrame()
        {
            WFFrame frame = new WFFrame();
            frame.ProcessId = this.ProcessId;
            frame.Sequence = this.FrameCount;
            this.FrameCount++;

            return frame;
        }

        public WFThread MainThread
        {
            get
            {
                WFThread thread = GetThread(MainThreadId);
                if (thread == null)
                    throw new WorkflowException("The main thread is missing.");
                return thread;
            }
        }

        public WFThread GetThread(int id)
        {
            WFThread thread = this.ThreadsInternal.SingleOrDefault(o => o.ThreadId == id);
            return thread;
        }

        internal WFStep GetStep(int sequence)
        {
            return ThreadsInternal.SelectMany(o => o.StepsInternal).SingleOrDefault(o => o.Sequence == sequence);
        }

        public WFThread[] AllThreads
        {
            get
            {
                return ThreadsInternal.ToArray();
            }
        }

        public WFStep[] ActiveSteps
        {
            get
            {
                List<WFStep> list = new List<WFStep>(5);
                foreach (WFThread thread in ThreadsInternal)
                {
                    if (!FlowControl.IsComponentFinishedCore(thread))
                    {
                        WFStep step = thread.ActiveStep;
                        if (step != null)
                            list.Add(step);
                    }
                }
                return list.ToArray();
            }
        }

        private ProcessProperties _properties;

        internal ProcessProperties Properties
        {
            get
            {
                if (_properties == null)
                {
                    _properties = ProcessProperties.Parse(_PropertiesData);
                    if (_properties == null)
                        throw new System.IO.InvalidDataException("Process property data corrupted.");
                    SetupProperties(false);
                }
                return _properties;
            }
        }

        private void SetupProperties(bool upateImmed)
        {
            System.ComponentModel.PropertyChangedEventHandler handler = (s, e) =>
            {
                System.Diagnostics.Debug.Assert(_properties != null);
                this.PropertiesData = _properties.ToString();
            };

            _properties.PropertyChanged += handler;

            if (upateImmed) handler(_properties, new System.ComponentModel.PropertyChangedEventArgs(null));
        }

        public ComponentIdentifier ID
        {
            get { return new ComponentIdentifier(this._ProcessId); }
        }

        IWorkflowComponent IWorkflowComponent.OwnerComponent
        {
            get { return null; }
        }

        internal static WFProcess[] QueryProcess(ComponentQueryArguments args, PagingInfo paging)
        {
            using (var provider = args.CreateProvider())
            {
                provider.InitializeProcessQuery(args);
                var q = ParseQuery(args, provider.WFProcesses);

                return QueryExecutor.ApplySortOrder(q, args.Sort).Page(paging).ToArray();
            }
        }

        private static IQueryable<WFProcess> ParseQuery(ComponentQueryArguments args, IQueryable<WFProcess> q)
        {
            if (args.WorkflowName != null)
            {
                List<string> list = args.WorkflowNames;
                if (list.Count == 1)
                    q = q.Where(o => o.WorkflowName == args.WorkflowName);
                else
                    q = q.Where(o => list.Contains(o.WorkflowName));
            }

            if (args.ComponentIDCount > 0)
            {
                var list = args.ComponentIDs;
                if (list.Count == 1)
                    q = q.Where(o => o.ProcessId == list[0].ProcessId);
                else
                {
                    Guid[] array = list.Select(o => o.ProcessId).ToArray();
                    q = q.Where(o => array.Contains(o.ProcessId));
                }
            }

            if (args.StatusCount > 0 || args.IsActive.HasValue)
            {
                List<WorkflowComponentStatus> list;
                if (args.IsActive.HasValue)
                {
                    list = new List<WorkflowComponentStatus>(2);
                    if (args.Status.Contains(WorkflowComponentStatus.Active))
                        list.Add(WorkflowComponentStatus.Active);
                    if (args.Status.Contains(WorkflowComponentStatus.Idling))
                        list.Add(WorkflowComponentStatus.Idling);
                    if (list.Count == 0)
                    {
                        list.Add(WorkflowComponentStatus.Idling);
                        list.Add(WorkflowComponentStatus.Active);
                    }
                }
                else
                    list = args.Status;

                if (list.Count == 1)
                    q = q.Where(o => o.Status == list[0]);
                else
                    q = q.Where(o => list.Contains(o.Status));
            }

            if (args.StageNameCount > 0)
            {
                var list = args.StageNames;
                if (list.Count > 1)
                {
                    if (args.IsActive.HasValue)
                    {
                        bool active = args.IsActive.Value;
                        q = q.Where(p => p.ThreadsInternal.Any(t => t.StepsInternal.Any(s => s.IsActive == active && list.Contains(s.StageName))));
                    }
                    else
                        q = q.Where(p => p.ThreadsInternal.Any(t => t.StepsInternal.Any(s => list.Contains(s.StageName))));
                }
                else
                {
                    if (args.IsActive.HasValue)
                    {
                        bool active = args.IsActive.Value;
                        q = q.Where(p => p.ThreadsInternal.Any(t => t.StepsInternal.Any(s => s.IsActive == active && list[0] == s.StageName)));
                    }
                    else
                        q = q.Where(p => p.ThreadsInternal.Any(t => t.StepsInternal.Any(s => s.StageName == list[0])));
                }
            }

            //if (args.AppKey != null)
            //{
            //    string key = args.AppKey;
            //    q = q.Where(o => o.AppKey.Contains(key) || o.AppName.Contains(key));
            //}

            return q;
        }

        public WFProcess Clone()
        {
            WFProcess proc = (WFProcess)this.MemberwiseClone();
            proc.Initialize();
            foreach (var t in ThreadsInternal)
                proc.ThreadsInternal.Add(t.Clone());
            return proc;
        }

        object ICloneable.Clone()
        {
            return this.Clone();
        }
    }
}
